package main

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"go-websocket/app"
	"go-websocket/app/model"
	"log"
	"net/http"
	"os"
	"regexp"
	"sync"
	"sync/atomic"
	"time"
)

/*
聊天室：（有用到redis）
	登录：输入用户名登录，校验用户是否在线
	下线：离线
	注销：注销用户（未实现）
	群聊：在公共频道发送消息，全部人可见
	私聊：选择用户，给这个人发送消息，仅那一个人可见
	留言：用户下线后，其她人给这个人发送消息，是留言
*/

// 定义WebSocket连接的升级器。升级器是一个http.HandlerFunc，它将HTTP连接升级为WebSocket连接
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}
var (
	WebScoketConnMap       = make(map[string]*websocket.Conn) // 存储连接对象，key 为用户名，value 为连接对象
	messageQueue           = make(chan model.Message, 100)    // 创建一个带缓冲的通道
	maxConnections   int32 = 100                              // 设置最大连接数
)
var clientsMux sync.RWMutex
var currentConnections int32 // 当前连接数

func main() {
	app.InitConfig() // 初始化配置
	// 启动消息处理 goroutine
	go func() {
		for msg := range messageQueue {
			// 发送消息
			if msg.Conn != nil {
				err := msg.Conn.WriteMessage(websocket.TextMessage, msg.Msg.EncodeMessage())
				if err != nil {
					log.Println("发送消息失败:", err)
				}
			}
		}
	}()

	http.HandleFunc("/web-socket", func(w http.ResponseWriter, r *http.Request) {
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println("err====>>>", err)
			return
		}
		// 连接数检查
		if atomic.LoadInt32(&currentConnections) >= maxConnections {
			conn.WriteMessage(websocket.TextMessage, []byte("连接数已达到上限"))
			conn.Close()
			return
		}
		atomic.AddInt32(&currentConnections, 1)        // 连接数+1
		defer atomic.AddInt32(&currentConnections, -1) // 关闭连接，连接数-1

		go handleConnection(conn, r)
	})
	log.Fatal(http.ListenAndServe(":7070", nil))
}

func handleConnection(conn *websocket.Conn, r *http.Request) {
	defer conn.Close()
	user := model.User{}
	data := r.FormValue("data") // 获取连接的数据
	err := json.Unmarshal([]byte(data), &user)
	if err != nil {
		conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
		return
	}

	// 检查用户是否已连接
	if getConnection(user.Name) != nil {
		conn.WriteMessage(websocket.TextMessage, []byte("该用户已连接，不允许重复连接"))
		return
	}
	err = user.EncodingBase64() // 解码用户头像
	if err != nil {
		conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
		return
	}
	addConnectionToMap(user.Name, conn) // 将当前连接添加到连接对象中
	goLive(user)                        // 用户上线
	// 处理消息
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			conn.WriteMessage(websocket.TextMessage, []byte("连接已关闭"))
			log.Println(conn.RemoteAddr().String(), "关闭连接", err)
			break
		}
		// 解析消息
		msg := model.Msg{}
		err = msg.ParseMessage(message)
		if err != nil {
			log.Println(err)
			break
		}
		if msg.IsPublic {
			publicMessage(msg) // 群聊消息
		} else {
			privateMessage(msg) // 私聊消息
		}
	}
	offLine(user) // 用户下线
}

// 用户上线
func goLive(user model.User) {
	userJSON, _ := json.Marshal(user)
	// 将当前用户信息添加到redis中
	err := app.RedisConn.HSet(app.UserListCache, user.Name, userJSON).Err()
	if err != nil {
		log.Println(err)
	}
	str := fmt.Sprintf("%s 加入聊天室，当前聊天室人数为 %d。", user.Name, getConnectionCount())
	fmt.Println(str)
	// 发送上线消息给其他用户
	msg := model.Msg{
		SendUser: user.Name,
		SendTime: time.Now().Format("2006-01-02 15:04:05"), // 日期格式化为 yyyy-MM-dd HH:mm:ss 格式
		Msg:      str,
		IsPublic: true,
		IsSend:   false,
		Pic:      user.Pic,
		PicName:  user.PicName,
	}
	publicMessage(msg)
	privateMessageHandle(user)
}

// 用户上线，处理自己的留言消息
func privateMessageHandle(user model.User) {
	var err error
	var cursor uint64
	var keys []string // 获取所有以 go-websocket:msgList开头的key
	keys, cursor, err = app.RedisConn.Scan(cursor, app.MsgListCache+":*", 0).Result()
	if err != nil {
		log.Println(err)
	}
	// 对每个匹配的key使用LRANGE命令获取列表中的值
	for _, key := range keys {
		// 使用正则表达式匹配发送人或接收人信息
		str := app.MsgListCache + ":(.*)TO(.*)" // go-websocket:msgList:发送人TO接收人
		receiverRegex := regexp.MustCompile(str)
		matches := receiverRegex.FindStringSubmatch(key)
		// 当前用户是发送人或接收人时
		if len(matches) == 3 && (matches[1] == user.Name || matches[2] == user.Name) {
			messages, err := app.RedisConn.LRange(key, 0, -1).Result()
			if err != nil {
				log.Println(err)
			}
			for _, v := range messages {
				var msg model.Msg
				err := json.Unmarshal([]byte(v), &msg)
				if err != nil {
					log.Println(err)
				}
				if matches[1] == user.Name { // 当前用户是发送人
					msg.SendUser = "我"
					msg.Pic = user.Pic
				}
				if matches[2] == user.Name { // 当前用户是接收人
					msg.Pic = ""
					if getConnection(msg.SendUser) != nil {
						r, _ := app.RedisConn.HGet(app.UserListCache, msg.SendUser).Result()
						u := model.User{}
						json.Unmarshal([]byte(r), &u)
						msg.Pic = u.Pic // 发送人在线，则设置这条消息的头像为发送人的头像
					}
					app.RedisConn.Del(key) // 接收人上线后，删除留言
				}
				// 将留言信息添加到消息队列中
				messageQueue <- model.Message{Conn: getConnection(user.Name), Msg: msg}
			}
		}
	}
}

// 公共消息
func publicMessage(msg model.Msg) {
	clientsMux.RLock()
	defer clientsMux.RUnlock()
	for userName, conn := range WebScoketConnMap {
		if (msg.IsSend && userName != msg.SendUser) || !msg.IsSend {
			messageQueue <- model.Message{Conn: conn, Msg: msg}
		}
	}
}

// 发送私聊消息给指定用户
func privateMessage(msg model.Msg) {
	conn := getConnection(msg.ReceUser)
	if conn != nil { // 当接收人在线时，直接发送消息到消息队列
		messageQueue <- model.Message{Conn: conn, Msg: msg}
	} else { // 当接收人不在线时，先将消息存储到redis中
		msgJSON, err := json.Marshal(msg)
		if err != nil {
			log.Println(err)
		}
		// key格式：go-websocket:msgList:发送人TO接收人
		key := app.MsgListCache + ":" + msg.SendUser + "TO" + msg.ReceUser
		app.RedisConn.RPush(key, msgJSON)
	}
}

// 用户下线
func offLine(user model.User) {
	name := user.Name
	removeUser(user) // 删除用户
	str := fmt.Sprintf("%s 离开了聊天室，当前聊天室人数为 %d。", name, getConnectionCount())
	fmt.Println(str)
	// 发送下线消息给其他用户
	msg1 := model.Msg{
		SendUser: name,
		SendTime: time.Now().Format("2006-01-02 15:04:05"),
		Msg:      str,
		IsPublic: true,
		IsSend:   false,
		Pic:      user.Pic,
		PicName:  user.PicName,
	}
	publicMessage(msg1)
}

// 用户下线删除用户
func removeUser(user model.User) {
	clientsMux.Lock()
	defer clientsMux.Unlock()
	os.Remove(user.Pic) // 删除头像文件
	app.RedisConn.HDel(app.UserListCache, user.Name)
	delete(WebScoketConnMap, user.Name)
}

// 添加连接到 WebScoketConnMap
func addConnectionToMap(userName string, conn *websocket.Conn) {
	clientsMux.Lock()
	defer clientsMux.Unlock()
	WebScoketConnMap[userName] = conn
}

// 从 WebScoketConnMap 中获取连接
func getConnection(userName string) *websocket.Conn {
	clientsMux.RLock()
	conn := WebScoketConnMap[userName]
	clientsMux.RUnlock()
	return conn
}

// 从 WebScoketConnMap 中获取连接数
func getConnectionCount() int {
	clientsMux.RLock()
	defer clientsMux.RUnlock()
	return len(WebScoketConnMap)
}
